﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections.Generic;
using System.Linq;
using AcademicPlanner.Data;
using AcademicPlanner.Data.Entities;
using AcademicPlanner.Data.Repositories;
using AcademicPlanner.Framework.Libraries.Validation;
using AcademicPlanner.Domain.Models.Collections;
using AcademicPlanner.Domain.Components;

namespace AcademicPlanner.Domain.Models
{
	/// <summary>
	/// Person represents a person in the application. A Person has <c>Account</c>s through
	/// which he is granted access to the application's functionalities.
	/// </summary>
	/// <seealso cref="T:Account"/>
	/// <since>1.0</since>
    public class Person : Model
	{
		/// <summary>
		/// List of this Person's <c>Account</c>s.
		/// </summary>
		/// <since>1.0</since>
		protected AccountCollection accounts;


		/// <summary>
		/// Gets the data layer repository for this Person's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected override EntityRepository EntityRepo { get { return DomainMaster.DSAS.PersonEntityRepo; } }

		/// <summary>
		/// Gets this Person's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected PersonEntity Entity { get { return (PersonEntity)entity; } }

		/// <summary>
		/// Gets this Person's email address.
		/// </summary>
		/// <since>1.0</since>
		public string Email 
		{ 
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_PERSON, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_PERSON_EMAIL);
				return Entity.Email; 
			} 
		}

		/// <summary>
		/// Gets this Person's full name.
		/// </summary>
		/// <since>1.0</since>
		public string Name 
		{ 
			get 
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_PERSON, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_PERSON_NAME);
				return Entity.Name; 
			} 
		}

		/// <summary>
		/// Gets the list of this Person's <c>Account</c>s.
		/// </summary>
		/// <since>1.0</since>
		public AccountCollection Accounts 
		{
			get
			{
				SetAccounts();
				return accounts; 
			} 
		}


		/// <summary>
		/// No argument constructor; invokes the matching base class constructor.
		/// </summary>
		/// <seealso cref="M:AcademicPlanner.Domain.Model.Model"/>
		/// <since>1.0</since>
		public Person()
			: base()
		{
			
		}

		/// <summary>
		/// Initializes a Person with the given <c>PersonEntity</c> by invoking the matching base class constructor.
		/// </summary>
		/// <seealso cref="M:AcademicPlanner.Domain.Model.Model(Entity)"/>
		/// <since>1.0</since>
		public Person(PersonEntity pEntity) 
			: base(pEntity)
		{
			
		}


		private void SetAccounts()
		{
			if (accounts == null)
				ResetAccounts();
		}

		private void ResetAccounts()
		{
			accounts = new AccountCollection(Entity.AccountEntities);
		}

		/// <summary>
		/// Sets this Person's properties after validating the provided values.
		/// <c>null</c> values are ignored and the corresponding properties are
		/// left unchanged.
		/// </summary>
		/// <param name="pEmail">The new email address.</param>
		/// <param name="pName">The new full name.</param>
		/// <exception cref="T:InvalidModelPropertiesException">
		/// Thrown with an error code of <c>DomainErrorCode.M_INVALID_PERSON_PROPERTIES</c>
		/// if validation errors were returned.
		/// </exception>
		/// <seealso cref="M:Validate"/>
		/// <since>1.0</since>
		internal void Set(string pEmail, string pName)
		{
			var vErrs = Validate(pEmail, pName);

			if (vErrs.OuterErrors.Count > 0)
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_PERSON_PROPERTIES, vErrs);

			if (pEmail != null)
				Entity.Email = pEmail;
			if (pName != null)
				Entity.Name = pName;
		}

		/// <summary>
		/// Adds an <c>Account</c> to the list of this Person's <c>Account</c>s.
		/// This method adds the underlying <c>AccountEntity</c> of the <c>Account</c>
		/// to the list of <c>AccountEntity</c>s in the Person's underlying <c>PersonEntity</c>.
		/// </summary>
		/// <param name="pAccount">The <c>Account</c> to add.</param>
		/// <seealso cref="M:AcademicPlanner.Domain.Model.GetOtherModelEntity"/>
		/// <since>1.0</since>
		internal void AddAccount(Account pAccount)
		{
			SetAccounts();
			var vAccts = accounts.ToList();
			var vEnt = (AccountEntity)GetOtherModelEntity(pAccount);

			if (vAccts.Where(pAcct => pAcct.User.Name.ToLower().Equals(pAccount.User.Name.ToLower())).Count() > 0)
			{
				var vErrs = new InvalidModelPropertiesErrorCollection("Person");
				var vInnerErrs1 = new InvalidModelPropertiesErrorCollection("Account");
				var vInnerErrs2 = new InvalidModelPropertiesErrorCollection("User");
				
				vInnerErrs2.AddOuter("Name", InvalidModelPropertyErrorCode.NOT_UNIQUE);
				vInnerErrs1.AddInner("User", vInnerErrs2);
				vErrs.AddInner("Accounts", vInnerErrs1);
				throw new InvalidModelPropertiesException(DomainErrorCode.M_NOT_UNIQUE_PERSON_ACCOUNTS_USER_NAME,
															vErrs);
			}

			Entity.AddAccountEntity(vEnt);
			ResetAccounts();
		}

		/// <summary>
		/// Validates a possible value of a Person's email address. Emails must be
		/// not blank and valid email addresses.
		/// </summary>
		/// <param name="pEmail">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsNotBlank"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsEmailAddress"/>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateEmail(string pEmail)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (!StringValidator.IsNotBlank(pEmail))
				rVal = InvalidModelPropertyErrorCode.REQUIRED;
			else if (!StringValidator.IsEmailAddress(pEmail))
				rVal = InvalidModelPropertyErrorCode.INVALID_EMAIL;

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of a Person's full name. Names must be
		/// not blank and alpha words.
		/// </summary>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsNotBlank"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsAlpha"/>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateName(string pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (!StringValidator.IsNotBlank(pVal))
				rVal = InvalidModelPropertyErrorCode.REQUIRED;
			else if (!StringValidator.IsAlpha(pVal))
				rVal = InvalidModelPropertyErrorCode.NOT_ALPHA;

			return rVal;
		}

		/// <summary>
		/// Validates possible values of a Person's properties. <c>null</c> values are ignored.
		/// </summary>
		/// <param name="pEmail">The email address.</param>
		/// <param name="pName">The full name.</param>
		/// <returns>A Collection of validation errors.</returns>
		/// <seealso cref="M:ValidateName"/>
		/// <seealso cref="M:ValidateEmail"/>
		/// <since>1.0</since>
		public static InvalidModelPropertiesErrorCollection Validate(string pEmail, string pName)
		{
			var rVal = new InvalidModelPropertiesErrorCollection("Person");
			InvalidModelPropertyErrorCode vCode;

			if (pEmail != null)
			{
				if (!(vCode = ValidateEmail(pEmail)).Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("Email", vCode);
			}

			if (pName != null)
			{
				if (!(vCode = ValidateName(pName)).Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("Name", vCode);
			}

			return rVal;
		}

    }
}
